1   /*
2    * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions
6    * are met:
7    *
8    *   - Redistributions of source code must retain the above copyright
9    *     notice, this list of conditions and the following disclaimer.
10   *
11   *   - Redistributions in binary form must reproduce the above copyright
12   *     notice, this list of conditions and the following disclaimer in the
13   *     documentation and/or other materials provided with the distribution.
14   *
15   *   - Neither the name of Oracle nor the names of its
16   *     contributors may be used to endorse or promote products derived
17   *     from this software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  
32  
33  import java.awt.Graphics;
34  import java.util.Stack;
35  import java.awt.event.*;
36  import java.util.ArrayList;
37  import java.util.List;
38  
39  
40  /**
41   * A (not-yet) Context sensitive L-System Fractal applet class.
42   *
43   * The rules for the Context L-system are read from the java.applet.Applet's
44   * attributes and then the system is iteratively applied for the
45   * given number of levels, possibly drawing each generation as it
46   * is generated.  Note that the ContextLSystem class does not yet
47   * handle the lContext and rContext attributes, although this
48   * class is already designed to parse the '[' and ']' characters
49   * typically used in Context sensitive L-Systems.
50   *
51   * @author      Jim Graham
52   */
53  @SuppressWarnings("serial")
54  public class CLSFractal
55          extends java.applet.Applet
56          implements Runnable, MouseListener {
57  
58      Thread kicker;
59      ContextLSystem cls;
60      int fractLevel = 1;
61      int repaintDelay = 50;
62      boolean incrementalUpdates;
63      float startAngle = 0;
64      float rotAngle = 45;
65      float Xmin;
66      float Xmax;
67      float Ymin;
68      float Ymax;
69      int border;
70      boolean normalizescaling;
71  
72      @Override
73      public void init() {
74          String s;
75          cls = new ContextLSystem(this);
76          s = getParameter("level");
77          if (s != null) {
78              fractLevel = Integer.parseInt(s);
79          }
80          s = getParameter("incremental");
81          if (s != null) {
82              incrementalUpdates = s.equalsIgnoreCase("true");
83          }
84          s = getParameter("delay");
85          if (s != null) {
86              repaintDelay = Integer.parseInt(s);
87          }
88          s = getParameter("startAngle");
89          if (s != null) {
90              startAngle = Float.valueOf(s).floatValue();
91          }
92          s = getParameter("rotAngle");
93          if (s != null) {
94              rotAngle = Float.valueOf(s).floatValue();
95          }
96          rotAngle = rotAngle / 360 * 2 * 3.14159265358f;
97          s = getParameter("border");
98          if (s != null) {
99              border = Integer.parseInt(s);
100         }
101         s = getParameter("normalizescale");
102         if (s != null) {
103             normalizescaling = s.equalsIgnoreCase("true");
104         }
105         addMouseListener(this);
106     }
107 
108     @Override
109     public void destroy() {
110         removeMouseListener(this);
111     }
112 
113     @Override
114     public void run() {
115         Thread me = Thread.currentThread();
116         boolean needsRepaint = false;
117         while (kicker == me && cls.getLevel() < fractLevel) {
118             cls.generate();
119             if (kicker == me && incrementalUpdates) {
120                 repaint();
121                 try {
122                     Thread.sleep(repaintDelay);
123                 } catch (InterruptedException ignored) {
124                 }
125             } else {
126                 needsRepaint = true;
127             }
128         }
129         if (kicker == me) {
130             kicker = null;
131             if (needsRepaint) {
132                 repaint();
133             }
134         }
135     }
136 
137     @Override
138     public void start() {
139         kicker = new Thread(this);
140         kicker.start();
141     }
142 
143     @Override
144     public void stop() {
145         kicker = null;
146     }
147 
148     /*1.1 event handling */
149     @Override
150     public void mouseClicked(MouseEvent e) {
151     }
152 
153     @Override
154     public void mousePressed(MouseEvent e) {
155     }
156 
157     @Override
158     public void mouseReleased(MouseEvent e) {
159         cls = new ContextLSystem(this);
160         savedPath = null;
161         start();
162         e.consume();
163     }
164 
165     @Override
166     public void mouseEntered(MouseEvent e) {
167     }
168 
169     @Override
170     public void mouseExited(MouseEvent e) {
171     }
172     String savedPath;
173 
174     @Override
175     public void paint(Graphics g) {
176         String fractalPath = cls.getPath();
177         if (fractalPath == null) {
178             super.paint(g);
179             return;
180         }
181         if (savedPath == null || !savedPath.equals(fractalPath)) {
182             savedPath = fractalPath;
183             render(null, fractalPath);
184         }
185 
186         for (int i = 0; i < border; i++) {
187             g.draw3DRect(i, i, getSize().width - i * 2, getSize().height - i * 2,
188                     false);
189         }
190         render(g, fractalPath);
191     }
192 
193     void render(Graphics g, String path) {
194         Stack<CLSTurtle> turtleStack = new Stack<CLSTurtle>();
195         CLSTurtle turtle;
196 
197         if (g == null) {
198             Xmin = 1E20f;
199             Ymin = 1E20f;
200             Xmax = -1E20f;
201             Ymax = -1E20f;
202             turtle = new CLSTurtle(startAngle, 0, 0, 0, 0, 1, 1);
203         } else {
204             float frwidth = Xmax - Xmin;
205             if (frwidth == 0) {
206                 frwidth = 1;
207             }
208             float frheight = Ymax - Ymin;
209             if (frheight == 0) {
210                 frheight = 1;
211             }
212             float xscale = (getSize().width - border * 2 - 1) / frwidth;
213             float yscale = (getSize().height - border * 2 - 1) / frheight;
214             int xoff = border;
215             int yoff = border;
216             if (normalizescaling) {
217                 if (xscale < yscale) {
218                     yoff += ((getSize().height - border * 2)
219                             - ((Ymax - Ymin) * xscale)) / 2;
220                     yscale = xscale;
221                 } else if (yscale < xscale) {
222                     xoff += ((getSize().width - border * 2)
223                             - ((Xmax - Xmin) * yscale)) / 2;
224                     xscale = yscale;
225                 }
226             }
227             turtle = new CLSTurtle(startAngle, 0 - Xmin, 0 - Ymin,
228                     xoff, yoff, xscale, yscale);
229         }
230 
231         for (int pos = 0; pos < path.length(); pos++) {
232             switch (path.charAt(pos)) {
233                 case '+':
234                     turtle.rotate(rotAngle);
235                     break;
236                 case '-':
237                     turtle.rotate(-rotAngle);
238                     break;
239                 case '[':
240                     turtleStack.push(turtle);
241                     turtle = new CLSTurtle(turtle);
242                     break;
243                 case ']':
244                     turtle = turtleStack.pop();
245                     break;
246                 case 'f':
247                     turtle.jump();
248                     break;
249                 case 'F':
250                     if (g == null) {
251                         includePt(turtle.X, turtle.Y);
252                         turtle.jump();
253                         includePt(turtle.X, turtle.Y);
254                     } else {
255                         turtle.draw(g);
256                     }
257                     break;
258                 default:
259                     break;
260             }
261         }
262     }
263 
264     void includePt(float x, float y) {
265         if (x < Xmin) {
266             Xmin = x;
267         }
268         if (x > Xmax) {
269             Xmax = x;
270         }
271         if (y < Ymin) {
272             Ymin = y;
273         }
274         if (y > Ymax) {
275             Ymax = y;
276         }
277     }
278 
279     @Override
280     public String getAppletInfo() {
281         return "Title: CLSFractal 1.1f, 27 Mar 1995 \nAuthor: Jim Graham \nA "
282                 + "(not yet) Context Sensitive L-System production rule. \n"
283                 + "This class encapsulates a production rule for a Context "
284                 + "Sensitive\n L-System \n(pred, succ, lContext, rContext)."
285                 + "  The matches() method, however, does not \n(yet) verify "
286                 + "the lContext and rContext parts of the rule.";
287     }
288 
289     @Override
290     public String[][] getParameterInfo() {
291         String[][] info = {
292             { "level", "int", "Maximum number of recursions.  Default is 1." },
293             { "incremental", "boolean", "Whether or not to repaint between "
294                 + "recursions.  Default is true." },
295             { "delay", "integer", "Sets delay between repaints.  Default is 50." },
296             { "startAngle", "float", "Sets the starting angle.  Default is 0." },
297             { "rotAngle", "float", "Sets the rotation angle.  Default is 45." },
298             { "border", "integer", "Width of border.  Default is 2." },
299             { "normalizeScale", "boolean", "Whether or not to normalize "
300                 + "the scaling.  Default is true." },
301             { "pred", "String",
302                 "Initializes the rules for Context Sensitive L-Systems." },
303             { "succ", "String",
304                 "Initializes the rules for Context Sensitive L-Systems." },
305             { "lContext", "String",
306                 "Initializes the rules for Context Sensitive L-Systems." },
307             { "rContext", "String",
308                 "Initializes the rules for Context Sensitive L-Systems." }
309         };
310         return info;
311     }
312 }
313 
314 
315 /**
316  * A Logo turtle class designed to support Context sensitive L-Systems.
317  *
318  * This turtle performs a few basic maneuvers needed to support the
319  * set of characters used in Context sensitive L-Systems "+-fF[]".
320  *
321  * @author      Jim Graham
322  */
323 class CLSTurtle {
324 
325     float angle;
326     float X;
327     float Y;
328     float scaleX;
329     float scaleY;
330     int xoff;
331     int yoff;
332 
333     public CLSTurtle(float ang, float x, float y,
334             int xorg, int yorg, float sx, float sy) {
335         angle = ang;
336         scaleX = sx;
337         scaleY = sy;
338         X = x * sx;
339         Y = y * sy;
340         xoff = xorg;
341         yoff = yorg;
342     }
343 
344     public CLSTurtle(CLSTurtle turtle) {
345         angle = turtle.angle;
346         X = turtle.X;
347         Y = turtle.Y;
348         scaleX = turtle.scaleX;
349         scaleY = turtle.scaleY;
350         xoff = turtle.xoff;
351         yoff = turtle.yoff;
352     }
353 
354     public void rotate(float theta) {
355         angle += theta;
356     }
357 
358     public void jump() {
359         X += (float) Math.cos(angle) * scaleX;
360         Y += (float) Math.sin(angle) * scaleY;
361     }
362 
363     public void draw(Graphics g) {
364         float x = X + (float) Math.cos(angle) * scaleX;
365         float y = Y + (float) Math.sin(angle) * scaleY;
366         g.drawLine((int) X + xoff, (int) Y + yoff,
367                 (int) x + xoff, (int) y + yoff);
368         X = x;
369         Y = y;
370     }
371 }
372 
373 
374 /**
375  * A (non-)Context sensitive L-System class.
376  *
377  * This class initializes the rules for Context sensitive L-Systems
378  * (pred, succ, lContext, rContext) from the given java.applet.Applet's attributes.
379  * The generate() method, however, does not (yet) apply the lContext
380  * and rContext parts of the rules.
381  *
382  * @author      Jim Graham
383  */
384 class ContextLSystem {
385 
386     String axiom;
387     List<CLSRule> rules = new ArrayList<CLSRule>();
388     int level;
389 
390     public ContextLSystem(java.applet.Applet app) {
391         axiom = app.getParameter("axiom");
392         int num = 1;
393         while (true) {
394             String pred = app.getParameter("pred" + num);
395             String succ = app.getParameter("succ" + num);
396             if (pred == null || succ == null) {
397                 break;
398             }
399             rules.add(new CLSRule(pred, succ,
400                     app.getParameter("lContext" + num),
401                     app.getParameter("rContext" + num)));
402             num++;
403         }
404         currentPath = new StringBuffer(axiom);
405         level = 0;
406     }
407 
408     public int getLevel() {
409         return level;
410     }
411     StringBuffer currentPath;
412 
413     public synchronized String getPath() {
414         return ((currentPath == null) ? null : currentPath.toString());
415     }
416 
417     private synchronized void setPath(StringBuffer path) {
418         currentPath = path;
419         level++;
420     }
421 
422     public void generate() {
423         StringBuffer newPath = new StringBuffer();
424         int pos = 0;
425         while (pos < currentPath.length()) {
426             CLSRule rule = findRule(pos);
427             if (rule == null) {
428                 newPath.append(currentPath.charAt(pos));
429                 pos++;
430             } else {
431                 newPath.append(rule.succ);
432                 pos += rule.pred.length();
433             }
434         }
435         setPath(newPath);
436     }
437 
438     public CLSRule findRule(int pos) {
439         for (int i = 0; i < rules.size(); i++) {
440             CLSRule rule = rules.get(i);
441             if (rule.matches(currentPath, pos)) {
442                 return rule;
443             }
444         }
445         return null;
446     }
447 }
448 
449 
450 /**
451  * A Context sensitive L-System production rule.
452  *
453  * This class encapsulates a production rule for a Context sensitive
454  * L-System (pred, succ, lContext, rContext).
455  * The matches() method, however, does not (yet) verify the lContext
456  * and rContext parts of the rule.
457  *
458  * @author      Jim Graham
459  */
460 class CLSRule {
461 
462     String pred;
463     String succ;
464     String lContext;
465     String rContext;
466 
467     public CLSRule(String p, String d, String l, String r) {
468         pred = p;
469         succ = d;
470         lContext = l;
471         rContext = r;
472     }
473 
474     public boolean matches(StringBuffer sb, int pos) {
475         if (pos + pred.length() > sb.length()) {
476             return false;
477         }
478         char cb[] = new char[pred.length()];
479         sb.getChars(pos, pos + pred.length(), cb, 0);
480         return pred.equals(new String(cb));
481     }
482 }